//
//  XMTimerTarget.swift
//  sinaDemo
//
//  Created by Xueming on 2021/3/31.
//

import UIKit
//使用中间类 创建一个继承 NSObject 的子类 MyTimerTarget，并创建开启计时器的方法。
//Timer依赖于NSRunLoop，Timer的创建和销毁必须在同一个线程。跨线程就操作不了
class XMTimerTarget: NSObject {
    weak var outTarget:NSObjectProtocol? = nil
    var outSelector:Selector? = nil
    
    static func scheduledTimerWithTimeInterval(interval:TimeInterval,target:NSObjectProtocol?,selector:Selector?,userInfo:Any?,repeats:Bool) -> Timer {
        let xmtimerTarget:XMTimerTarget = XMTimerTarget.init()
        xmtimerTarget.outTarget = target
        xmtimerTarget.outSelector = selector
        /*
         kCFRunLoopDefaultMode：App的默认运行模式，通常主线程是在这个运行模式下运行
         UITrackingRunLoopMode：跟踪用户交互事件（用于 ScrollView 追踪触摸滑动，保证界面滑动时不受其他Mode影响）
         UIInitializationRunLoopMode：在刚启动App时第进入的第一个 Mode，启动完成后就不再使用
         GSEventReceiveRunLoopMode：接受系统内部事件，通常用不到
         kCFRunLoopCommonModes：伪模式，不是一种真正的运行模式（后边会用到）
         */
        //        let timer = Timer.scheduledTimer(timeInterval: interval, target: xmtimerTarget, selector: #selector(self.timerSelector(timer:)), userInfo: userInfo, repeats: repeats)
        /*
         Timer.scheduledTimer.这句代码调用了scheduledTimer返回的定时器，NSTimer会自动被加入到了RunLoop的NSDefaultRunLoopMode模式下。当滑动时候不会执行
         */
        
        let timer =  Timer.init(timeInterval: interval, target: xmtimerTarget, selector: #selector(self.timerSelector(timer:)), userInfo: userInfo, repeats: repeats)
        RunLoop.current.add(timer, forMode: .common)
        return timer
    }
    @objc private func timerSelector(timer:Timer){
        if outTarget != nil && ((outTarget?.responds(to: outSelector)) != nil) {
            _ = outTarget!.perform(outSelector!)
        }else {
            timer.invalidate()
        }
    }
    deinit {
        print("XMTimerTarget deinit")
    }
}

//使用 weakProxy 创建一个继承 NSProxy 的子类 XMProxy，并实现消息转发的相关方法。
//NSProxy 是 iOS 开发中一个消息转发的基类，它不继承自 NSObject。因为他也是 Foundation 框架中的基类, 通常用来实现消息转发, 我们可以用它来包装 NSTimer 的 target, 达到弱引用的效果。
//交换方法
class XMProxy: NSObject {
    weak var target:NSObjectProtocol?
    var sel:Selector?
    /// required，实例化timer之后需要将timer赋值给proxy，否则就算target释放了，timer本身依然会继续运行
    public weak var timer:Timer?
    
    public required init(target:NSObjectProtocol?, sel:Selector?) {
        self.target = target
        self.sel = sel
        super.init()
        // 加强安全保护
        guard target?.responds(to: sel) == true else {
            return
        }
        // 将target的selector替换为redirectionMethod，该方法会重新处理事件
        let method = class_getInstanceMethod(self.classForCoder, #selector(XMProxy.redirectionMethod))!
        class_replaceMethod(self.classForCoder, sel!, method_getImplementation(method), method_getTypeEncoding(method))
    }
    
    @objc func redirectionMethod () {
        // 如果target未被释放，则调用target方法，否则释放timer
        if self.target != nil {
            self.target!.perform(self.sel)
        } else {
            self.timer?.invalidate()
            print("XMProxy: invalidate timer.")
        }
    }
}
extension Timer {
    
    public class func xm_scheduledTimer(timeInterval ti: TimeInterval, target aTarget: NSObjectProtocol, selector aSelector: Selector, userInfo aInfo: Any?, repeats yesOrNo: Bool) -> Timer {
        let proxy = XMProxy.init(target: aTarget, sel: aSelector)
        let timer = Timer.scheduledTimer(timeInterval: ti, target: proxy, selector: aSelector, userInfo:aInfo, repeats: yesOrNo)
        proxy.timer = timer
        return timer
    }
}
// MARK: - CADisplayLink--同样基于NSRunLoop依托于设备屏幕刷新频率触发事件，所以其触发时间比NSTimer较准确，也是最适合做UI不断刷新的事件，过渡相对流畅，无卡顿感

class XMCADisplayLink{
    weak var outTarget:NSObjectProtocol? = nil
    var outSelector:Selector? = nil
    static func scheduledPreferredFramesPerSecond(interval:Int,target:NSObjectProtocol?,selector:Selector?) -> CADisplayLink {
        let xmtimerTarget:XMCADisplayLink = XMCADisplayLink.init()
        xmtimerTarget.outTarget = target
        xmtimerTarget.outSelector = selector
        let cadisplayLink = CADisplayLink.init(target: xmtimerTarget, selector: #selector(self.CALinkTimerSelector(link:)))
        cadisplayLink.add(to: RunLoop.current, forMode: .common)
        //每interval帧处理一次 大概 一秒60/interval次
        cadisplayLink.preferredFramesPerSecond = interval
        return cadisplayLink
    }
    @objc func CALinkTimerSelector(link:CADisplayLink){
        if outTarget != nil && ((outTarget?.responds(to: outSelector)) != nil) {
            _ = outTarget!.perform(outSelector!)
        }else {
            link.invalidate()
        }
    }
}
// MARK: - GCD定时器，相比之下GCD的定时器会更加准时，因为GCD不是依赖RunLoop，而是由内核决定。使用了dispatch源(dispatch source)，dispatch源监听系统内核对象并处理，通过系统级调用，更加精准。
typealias ActionBlock = () -> ()

class XMGCDTimer: NSObject {
    
    static let share = XMGCDTimer()
    
    lazy var timerContainer = [String : DispatchSourceTimer]()
    
    /// 创建一个名字为name的定时
    ///
    /// - Parameters:
    ///  - name: 定时器的名字
    ///  - timeInterval: 时间间隔
    ///  - queue: 线程
    ///  - repeats: 是否重复
    ///  - action: 执行的操作
    func scheduledDispatchTimer(withName name:String?, timeInterval:Double, queue:DispatchQueue, repeats:Bool, action:@escaping ActionBlock ) {
        if name == nil {
            return
        }
        var timer = timerContainer[name!]
        if timer==nil {
            timer = DispatchSource.makeTimerSource(flags: [], queue: queue)
            timer?.resume()
            timerContainer[name!] = timer
        }
        timer?.schedule(deadline: .now(), repeating: timeInterval, leeway: .milliseconds(100))
        timer?.setEventHandler(handler: { [weak self] in
            action()
            if repeats==false {
                self?.destoryTimer(withName: name!)
            }
        })
        
    }
    
    /// 销毁名字为name的计时器
    ///
    /// - Parameter name: 计时器的名字
    func destoryTimer(withName name:String?) {
        let timer = timerContainer[name!]
        if timer == nil {
            return
        }
        timerContainer.removeValue(forKey: name!)
        timer?.cancel()
    }
    
    /// 检测是否已经存在名字为name的计时器
    ///
    /// - Parameter name: 计时器的名字
    /// - Returns: 返回bool值
    func isExistTimer(withName name:String?) -> Bool {
        if timerContainer[name!] != nil {
            return true
        }
        return false
    }
    
}
